# -*- coding:utf-8 -*-
from Env import env_config as cfg
import os, time
from Util.excel_data import read_excel, set_style
from Config.case_field_config import get_case_special_field_list, get_not_null_field_list, get_list_field,\
    get_not_null_field_list_with_depend
from Util.mongodb import MongodbUtils
from Common.com_func import is_null, log, mongo_exception_send_DD, ping_host, send_DD, api_monitor_send_DD, mkdir
from Util.date_helper import get_current_iso_date, get_date_by_days
import re
from bson.objectid import ObjectId
from Config.case_field_config import get_case_field_name
from Core.verify_interface import VerifyInterface
from Core.acquire_depend import AcquireDependField
from Util.decorator_tools import async
import xlwt
from dateutil import parser
# sys.path.append("./")
from Common.model import db_model
from Util.tool import capture_str_variable, md5
from flask import Response, request, redirect
import json
import random
from concurrent.futures import ThreadPoolExecutor

"""
api 服务底层的业务逻辑
"""


def clear_test_result(pro_name, days):
    """
    删除 指定x天之前的 测试结果
    :param time:
    :param pro_name:
    :return:
    """
    date_str = get_date_by_days(days=days, time_type="%Y-%m-%dT%H:%M:%S")
    log.info("\n==============================\n")
    log.info("删除 " + pro_name + " 项目 " + date_str + " 之前的测试结果")
    with MongodbUtils(ip=cfg.MONGODB_ADDR, database=cfg.MONGODB_DATABASE, collection=pro_name + cfg.TABLE_RESULT) as pro_db:
        try:
            query = {"update_time": {"$lt": parser.parse(date_str)}}
            del_result = pro_db.delete_many(query)
            log.info("共删除：" + str(del_result.deleted_count) + " 条记录！")
            log.info("\n==============================\n")
        except Exception as e:
            mongo_exception_send_DD(e=e, msg="删除'" + pro_name + "'项目指定日期前的测试结果")
            return "mongo error"


def clear_excel_logs(pro_name, time):
    """
    删除指定时间之前 生成的 excel 和 日志
      -mmin +1 -> 表示1分钟前的
      -mtime +1 -> 表示1天前的
    :param time:
    :param pro_name:
    :return:
    """
    rm_log_cmd = "find '" + cfg.LOGS_DIR + "' -name '*.log' -mmin +" + str(time) + " -type f -exec rm -rf {} \\;"
    rm_report_cmd = "find '" + cfg.REPORTS_DIR + pro_name + "/history' -name '*.xls' -mmin +" + str(time) + \
                    " -type f -exec rm -rf {} \\;"
    print(rm_log_cmd)
    print(rm_report_cmd)
    os.system(rm_log_cmd)
    os.system(rm_report_cmd)


def retry(func):
    """
    重 试 ( 判断项目是否运行中 )
    """
    def wrapper(*args, **kwargs):
        try_cnt = 0
        # 获取原函数中的重试次数
        try_limit = kwargs.get("try_limit", 0)
        interval_time = kwargs.get("interval_time", 1)
        if try_limit == 0:
            return func(*args, **kwargs)
        else:
            while try_cnt < try_limit:
                t_f = func(*args, **kwargs)
                if t_f:
                    time.sleep(interval_time)
                    try_cnt += 1
                    if try_limit != 1:
                        log.info(f"判断项目是否正在运行：第 {str(try_cnt)} 次 判 断")
                else:
                    return False
            return True

    return wrapper


@retry
def pro_is_running(pro_name, try_limit=0, interval_time=1):
    """
    判断项目是否在运行
    :param pro_name:
    :param try_limit: 重试次数 (传递给装饰器使用)
    :return:
    """
    dm = db_model(pro_name=pro_name, table=cfg.TABLE_CONFIG, error_msg="获取项目运行状态")
    res_dict = dm.query_db(filter={"config_value": 1, "_id": 0}, query={"config_type": "status", "config_name": "run"},
                           is_many=False)
    return res_dict.get("config_value")


def set_pro_run_status(pro_name, run_status=False):
    """
    设置项目的'运行状态'
    :param pro_name:
    :param run_status:
    :return:
    """
    dm = db_model(pro_name=pro_name, table=cfg.TABLE_CONFIG, error_msg="设置项目的运行状态")
    dm.update_db(query={"config_type": "status", "config_name": "run"}, set_dict={"config_value": run_status})


def get_run_type_status(pro_name, run_type):
    """
    获取 运行方式 任务状态
    :param pro_name:
    :param run_type: cron | deploy
    :return:
    """
    run_type_name = run_type == "cron" and "定时任务" or "部署测试"
    dm = db_model(pro_name=pro_name, table=cfg.TABLE_CONFIG, error_msg="获取"+run_type_name+"状态")
    result = dm.query_db(query={"config_name": run_type}, is_many=False)
    cron_status = result.get("config_value")
    return cron_status


def user_is_available(username):
    """
    查询用户是否已存在
    :param username:
    :return:
    """
    dm = db_model(table=cfg.USER_INFO, error_msg="查询用户是否已存在")
    if username:
        res_dict = dm.query_db(query={"username": username}, is_many=False)
        if res_dict:
            return False  # 用户名已存在
        else:
            return True  # 用户名不存在（可以注册）
    else:
        return False  # 用户名不能为空


def sign_in(username, password):
    """
     用 户 注 册
    :param username:
    :param password:
    :return:
    """
    dm = db_model(table=cfg.USER_INFO, error_msg="用户注册")
    data = {"username": username, "password": md5(password)}
    dm.insert_db(data=data, is_many=False)


def log_in(username, password, remind):
    """
    用 户 登 录
        登录成功后
        1.生成 token = MD5 ( 用户名 + 三个随机数 + 密码 )
        2.将token保存入数据库并设置 status=1
        3.将token存入cookie中作为响应头文件返回
    """
    res_dict = dict()
    dm = db_model(table=cfg.USER_INFO, error_msg="用户登录")
    user_dict = dm.query_db(query={"username": username}, is_many=False)
    if user_dict:
        if user_dict.get("password") == md5(password):
            res_dict["code"] = 200
            res_dict["msg"] = u"登录成功"
        else:
            res_dict["code"] = 204
            res_dict["msg"] = u"用户名或密码错误"
    else:
        res_dict["code"] = 205
        res_dict["msg"] = u"该用户不存在"

    response = Response(json.dumps(res_dict, ensure_ascii=False))  # 序列化：字典 -> 字符串
    if res_dict["code"] == 200:
        # 1.生成 token = MD5 ( 用户名 + 三个随机数 + 密码 )
        token = md5(username + str(random.randint(100, 999)) + password)
        # 2.将token保存入数据库并设置 status=1
        dm.update_db(query={"username": username}, set_dict={"token": token, "status": 1})
        # 3.将token存入cookie中作为响应头文件返回
        response.headers["Content-Type"] = "application/json; charest=utf-8"
        if remind == "on":  # 记住我
            response.set_cookie('token', token, max_age=604800, httponly=True)  # 7天 免登录 604800
        else:
            response.set_cookie('token', token, httponly=True)  # 将返回的token设置在响应头信息中
    return response


def recur_get_relation_list_by_case(upstream_case, upstream_case_list):
    """
    (递归) 通过'上层用例'获取用例关联列表（按照'关联'路径排序）
    :param upstream_case: 上层接口用例
    :param upstream_case_list: 上游接口用例列表
        举例：'场景一'
            通过 最上层关联用例：接口1
            获取 关联列表 [ 接口1, 接口2, 接口3 ]
        逻辑：
         1.若'name'匹配不到任何一个'upstream_relation_case'：说明关联列表只有一个用例（当前用例）
         2.若'name'可以匹配到'upstream_relation_case'：则进行递归匹配
    """
    upstream_list = []
    for downstream_case in upstream_case_list:
        if upstream_case.get('interface_name') == downstream_case.get('upstream_relation_case'):
            upstream_list += recur_get_relation_list_by_case(downstream_case, upstream_case_list)
            break
    upstream_list.insert(0, upstream_case)
    return upstream_list


def get_relation_list(pro_name, list_type="all", case_list=None, filter_online=False):
    """
        获取 关联用例列表(上线|所有、排序)
        :param pro_name:
        :param list_type: 列表类型：all(所有用例)、test(上线的测试类型用例)
        :param case_list: 用例列表
        :param filter_online: 过滤出上线用例（ 默认 False 表示不考虑上下线状态，显示全部关联 ）
        :return:

        < 备注 > 若 'list_type=all' 表示 从'_case'表中获取 case_list

        情况1：从'用例列表(所有)'中获取'关联用例列表(上线、排序)'
        传参：relation_list = get_relation_list(list_type="all", filter_online=True)

        情况2：从'用例列表(所有)'中获取'关联用例列表(所有、排序)'
        传参：relation_list = get_relation_list(list_type="all")

        情况3：从'测试类型用例列表(上线)'中获取'关联用例列表(上线、排序)'  《 使用处：执行测试类型用例 》
        传参：get_relation_list(list_type="test", case_list=test_list_online)

        < 情况3：可能需要处理的问题 >
        问 题：若关联链路中存在'下线'的用例，则该用例的'下游用例'不会出现在已排序的'关联列表'中，需要主动将其添加
        原始关联链路 [ 接口1(下线), 接口2(上线), 接口3(上线), 新增用户(上线), 删除用户(上线) ]
        已获取的关联 (relation_list)         [ 删除用户(上线), 接口3(上线), 新增用户(上线), 接口2(上线) ]
        已排序的关联 (relation_list_sorted)  [ 新增用户(上线), 删除用户(上线)]
            < 问题：因为最上游的'接口1'不存在，所以'接口2,接口3'无法获取到关联链路中 >
        已排序的关联 (relation_list_sorted)  [ 新增用户(上线), 删除用户(上线), 接口3(上线), 接口2(上线) ]
            < 解决：主动添加'不在链路中的'关联用例 >
    """
    if list_type == "all" and not case_list:
        dm = db_model(pro_name=pro_name, table=cfg.TABLE_CASE, error_msg="获取测试用例列表(_case)")
        case_list = dm.query_db()

    # 从'用例列表(所有)'中过滤出'关联接口列表(所有)' | 从'测试类型用例列表(上线)'中过滤出'关联接口列表(上线)'
    relation_list = list(filter(lambda x: x.get("upstream_relation_case") != "" or
                                          x.get("downstream_relation_field_name_list") != [], case_list))

    # 从'关联用例列表(所有|上线)'中获取'顶层上游接口列表(所有)'
    #  举例：从'场景一'的 [ 接口1, 接口2, 接口3, 测试1, 测试2, 测试3 ] 列表中 获取 [ 接口1, 测试1 ] （即：relation_case 为空）
    top_upstream_case_list_all = list(filter(lambda x: x.get('upstream_relation_case') == "", relation_list))

    # '关联用例列表(所有|上线)'根据关联进行排序
    #  举例：排序前 = [ 测试2, 接口3, 删除用户, 接口1, 测试3, 新增用户, 接口2, 测试1 ]
    #       排序后 = [ 接口1, 接口2, 接口3, 新增用户, 删除用户, 测试1, 测试2, 测试3 ]
    relation_list_sorted = []
    for case in top_upstream_case_list_all:
        relation_list_sorted += recur_get_relation_list_by_case(case, relation_list)

    # 判断 是否需要过滤'上线'的用例
    if filter_online:
        relation_list_sorted = list(filter(lambda case: case.get("case_status"), relation_list_sorted))

    # 判断 若'参数列表'是'测试类型用例列表(上线)' 且 '关联链路'中存在'下线'用例
    if list_type == "test" and len(relation_list_sorted) < len(relation_list):
        for case in relation_list:
            if case not in relation_list_sorted:
                relation_list_sorted.append(case)  # 主动添加'不在链路中的'关联用例

    return relation_list_sorted


def get_relation_list_by_case_name(pro_name, case_name):
    """
        根据 用例名称 获取相关关联用例列表
    """
    all_relation_list = get_relation_list(pro_name=pro_name, list_type="all")  # 获取 所有关联用例列表
    relation_list = []  # 当前关联列表（动态获取）
    for case in all_relation_list:
        relation_list.append(case)
        # 遍历到 关联链路 最底层的用例后，判断'当前关联列表'中是否存在'需要匹配的用例名称'
        if not case['downstream_relation_field_name_list']:
            if list(filter(lambda case: case.get("interface_name") == case_name, relation_list)):
                break
            else:
                relation_list = []
    return relation_list


def check_relation_case_status(pro_name, case_name):
    """
        检查 某用例的'关联用例'是否全部上线
    """
    relation_list = get_relation_list_by_case_name(pro_name, case_name)
    online_list = list(filter(lambda case: case.get("case_status"), relation_list))
    # for case in relation_list: print(f"{case['interface_name']}：{case['case_status']}")
    return len(relation_list) == len(online_list)


def run_test_by_pro(host, pro_name, run_type):
    """
    运行测试
    :param host：手动执行传入 host_url，定时任务传入 host_name
    :param pro_name:
    :param run_type: 运行方式：manual | cron | deploy
    :return:
        1.<判断>相关信息
           host是否可以ping通
        2.获取上线的接口列表
         （1）上线的'依赖接口列表'
         （2）上线的'测试接口列表'
          <判断>是否存在 上线的'测试接口列表'
        3.<判断>是否存在需要替换的全局变量
        （1）若不需要，直接下一步
        （2）若需要 <判断>需要替换的全局变量是否存在
             1）若不存在，则直接报错，终止流程
             2）若都存在，替换相关变量后，进入下一步
        4.若 存在错误信息
        （1）直接返回
        （2）若是 定时任务，则需要钉钉通知
        5.检查全局变量配置是否正确
        6.异步执行 接口测试
    """
    error_msg = ""
    run_type_name = run_type == "cron" and "定 时 任 务" or (run_type == "deploy" and "部 署 测 试" or "手 动 执 行")
    # 定时或部署测试 需判断该功能是否开启
    if run_type in ["cron", "deploy"]:
        if get_run_type_status(pro_name, run_type):
            host = get_host_url(pro_name, host)
        else:
            log.info("\n\n========================== " + run_type_name + " 已 关 闭 ==========================\n\n")
            return run_type + "任务已关闭"

    # 定时或部署测试 对判断项目运行状态 需设置重试次数
    if run_type in ["cron", "deploy"]:
        pro_run_status = pro_is_running(pro_name=pro_name, try_limit=5, interval_time=2)
    else:
        pro_run_status = pro_is_running(pro_name)
    if pro_run_status:
        error_msg = "当前项目正在运行中"
    else:
        if is_null(pro_name):
            error_msg = "项目名不能为空"
        elif is_null(host):
            error_msg = "HOST不能为空"
        elif not ping_host(host=host, check_num=5):
            error_msg = "本地无法 ping 通 HOST"

    if error_msg:
        if run_type in ["cron", "deploy"]:
            text = "#### '" + pro_name + "' 项 目 " + run_type_name + " 执 行 提 示：" + error_msg
            send_DD(dd_group_id=cfg.DD_MONITOR_GROUP, title=pro_name, text=text, at_phones=cfg.DD_AT_FXC, is_at_all=False)
        return error_msg

    dm = db_model(pro_name=pro_name, table=cfg.TABLE_CASE, error_msg="获取上线接口列表")
    interface_list = dm.query_db(query={"case_status": True})
    # 从'所有上线接口列表中'区分'依赖接口列表'和'测试接口列表'
    depend_interface_list = list(filter(lambda x: x.get("is_depend"), interface_list))
    test_interface_list = list(filter(lambda x: not x.get("is_depend"), interface_list))

    if is_null(test_interface_list):
        text = "#### '" + pro_name + "'项目 没有上线的用例"
        send_DD(dd_group_id=cfg.DD_MONITOR_GROUP, title=pro_name, text=text, at_phones=cfg.DD_AT_FXC, is_at_all=False)
        return"没有上线的用例"

    # 检查全局变量配置是否正确
    check_result, global_variable_dict = get_check_global_variable_result(pro_name)
    if check_result not in ["检查通过", "未使用全局变量"]:
        text = "#### '" + pro_name + "'项目 全局变量配置有误"
        send_DD(dd_group_id=cfg.DD_MONITOR_GROUP, title=pro_name, text=text, at_phones=cfg.DD_AT_FXC, is_at_all=False)
        return "全局变量配置有误"

    test_interface(pro_name=pro_name, host=host, depend_interface_list=depend_interface_list, run_type=run_type,
                   test_interface_list=test_interface_list, global_variable_dict=global_variable_dict)
    return "测试进行中"


@async
def test_interface(pro_name, host, depend_interface_list, test_interface_list, global_variable_dict, run_type):
    """
    【 测 试 接 口 】（根据项目名）
    :param pro_name:
    :param host:
    :param depend_interface_list:  依赖类型接口列表
    :param test_interface_list:    测试类型接口列表
    :param global_variable_dict:   全局变量字典
    :param run_type:  manual | cron | gitlab
    :return:

        【 测 试 流 程 】
        1.将项目'运行状态'设置为开启
        2.替换所有接口中的全局变量 ( 接口地址、请求头文件、请求参数 )
        3.获取依赖字段值
           < 判断 > 是否需要执行依赖（测试类型接口中是否需要引用依赖字段）：
         （1）若不需要 则 直接进入'验证接口'步骤
         （2）若需要 则获取依赖字段：
              1）若获取成功，则替换测试接口中的相应变量、进入'验证接口'步骤
              2）若获取失败，则不进行接口验证
              （ 备注：通过 'verify_flag' 标记进行控制 ）
        4.验证接口
        （1）将'测试类型接口列表'区分'非关联接口列表'和'关联接口列表'(按照关联度排序) < 参考调试代码：debug.py >
        （2）先执行关联接口列表的测试，再执行非关联接口列表的测试，并获取测试结果列表
        （3）更新测试结果
        5.将项目'运行状态'设置为停止
        6.将测试结果保存入 _result 数据库（仅上线用例）
        7.若存在'失败'或'错误'则发送钉钉
    """
    # 1.将项目'运行状态'设置为开启
    set_pro_run_status(pro_name=pro_name, run_status=True)

    # 2.替换所有接口中的全局变量 ( 接口地址、请求头文件、请求参数、请求正文体 )
    depend_interface_list = replace_global_variable(depend_interface_list, global_variable_dict)
    test_interface_list = replace_global_variable(test_interface_list, global_variable_dict)

    # 3.获取依赖字段值
    adf = AcquireDependField(pro_name=pro_name, host=host, depend_interface_list=depend_interface_list,
                             test_interface_list=test_interface_list)
    if adf.is_need_depend():
        test_interface_list, update_time = adf.acquire()
    else:
        update_time = get_current_iso_date()

    # 4.验证接口
    error_list = []
    fail_list = []
    if adf.verify_flag:

        # 测试重试次数使用（ 调试使用 ）
        # host = "http://192.168.31.111:1180/api_local"

        id_result_dict = {}  # 记录 测试结果列表 {"_id":{"test_result":"success", "":""}, "_id":{}, }

        # 先执行 关联接口列表(上线、排序)
        relation_dict = {}  # 记录 上下游关联字典 ( 由于关联字段捕获后在当前关联链路中就被使用掉了，即便之后出现重复的关联字段，也不会影响原有链路的取值 )
        relation_list_online = get_relation_list(pro_name=pro_name, list_type="test", case_list=test_interface_list)
        for case in relation_list_online:
            # 检查当前用例的关联用例是否全部上线
            all_relation_online = check_relation_case_status(pro_name=pro_name, case_name=case.get("interface_name"))
            # 执行
            id_result_dict[case.get("_id")], curren_relation_dict = \
                VerifyInterface(host=host, case=case, case_type="relation", relation_dict=relation_dict,
                                all_relation_online=all_relation_online).verify()
            # 循环 添加关联字典
            relation_dict.update(curren_relation_dict)

        # 再执行 非关联接口列表(上线)
        unrelation_list_online = list(filter(lambda x: x["upstream_relation_case"] == "" and
                                                       x["downstream_relation_field_name_list"] == [],
                                             test_interface_list))
        for case in unrelation_list_online:
            id_result_dict[case.get("_id")] = VerifyInterface(host=host, case=case).verify()

        # # 多线程 执行
        # def verify_func(case):
        #     id_result_dict[case.get("_id")] = VerifyInterface(host=host, case=case).verify()
        # pool = ThreadPoolExecutor(5)  # 使用线程池
        # for case in unrelation_list_online:
        #     pool.submit(verify_func, case)  # 启用多线程 调用方法
        # pool.shutdown()  # 等待所有线程执行完毕

        # 更新测试结果
        dm = db_model(pro_name=pro_name, table=cfg.TABLE_CASE, error_msg="更新测试结果")
        for _id, test_info in id_result_dict.items():
            test_info["update_time"] = update_time
            dm.update_db(query={"_id": _id}, set_dict=test_info, is_many=False)
            if "error" in test_info["test_result"]: error_list.append(test_info["test_result"])
            if "fail" in test_info["test_result"]: fail_list.append(test_info["test_result"])

    # 5.将项目'运行状态'设置为停止
    set_pro_run_status(pro_name=pro_name, run_status=False)

    # 6.将测试结果数据保存入 _result 数据库（最新一次运行时间的用例）
    save_test_result(pro_name, host, global_variable_dict, run_type)

    # 7.若存在'失败'或'错误'则发送钉钉（ 若依赖的接口测试标记为 False：表示依赖接口存在 fail 或 error  ）
    if adf.verify_flag:
        wrong_type = fail_list and "fail" or (error_list and "error" or "pass")
    else:
        wrong_type = "fail"
    if wrong_type != "pass":
        api_monitor_send_DD(pro_name=pro_name, run_type=run_type, test_time=update_time, wrong_type=wrong_type)


def save_test_result(pro_name, host, global_variable_dict, run_type):
    """
    将测试结果数据保存入 _result 数据库（最新一次运行时间的用例）
    （ 包括 host、 全局变量、运行类别 ）
    :return:
    """
    # 获取最新更新时间
    dm = db_model(pro_name=pro_name, table=cfg.TABLE_CASE, error_msg="获取所有上线的用例")
    last_update_time = dm.query_db(is_cursor=True).sort("update_time", -1)[0].get("update_time")
    last_update_time_case_list = dm.query_db(query={"update_time": last_update_time}, filter={"_id": 0})
    # 在查询结果中添加 host 和 全局变量字典
    for case in last_update_time_case_list:
        case["host"] = host
        case["global_variable_dict"] = global_variable_dict
        case["run_type"] = run_type

    # 将数据保存入 _result 数据库表
    dm_res = db_model(pro_name=pro_name, table=cfg.TABLE_RESULT, error_msg="将测试结果保存入_result表")
    dm_res.insert_db(data=last_update_time_case_list)


def replace_global_variable(interface_list, global_variable_dict):
    """
    替换接口列表中的全局变量 ( 接口地址、请求头文件、请求参数 )
    :param interface_list:
    :param global_variable_dict:
    :return:
    """
    for interface_dict in interface_list:
        for key in interface_dict.keys():
            if key in ["interface_url", "request_header", "request_params", "request_body"]:
                for v_name in global_variable_dict.keys():
                    interface_dict[key] = interface_dict[key].replace("<" + v_name + ">", global_variable_dict[v_name])
    return interface_list


def update_case_status_by_tag(pro_name, case_status, tag_name):
    """
    通过标签 设置用例状态（上下线）
    :param pro_name:
    :param case_status:  True、False
    :param tag_name:  all | no_tag | 回归 | 监控,回归 | 回归::A | 监控,回归::A |
    :return:
        <备注> re.compile(interface_name) # 表示 在mongo中 模糊匹配
    """
    case_status = case_status == "True" or False
    many_tag = False
    query_dict = {}
    set_dict = {"case_status": case_status}
    dm = db_model(pro_name=pro_name, table=cfg.TABLE_CASE, error_msg="通过标签设置用例状态")
    if tag_name == "all":  # 所有用例
        query_dict = {}
    elif tag_name == "no_tag":  # 未打标签用例
        query_dict = {"tag_name": ""}
    else:
        tmp = tag_name.split("::")
        has_A = len(tmp) == 2
        tag_name_list = tmp[0].split(",")
        if len(tag_name_list) == 1:  # 单标签用例
            query_dict = has_A and {"tag_name": tag_name_list[0]} or {"tag_name": re.compile(tag_name_list[0])}
        else:  # 多标签用例 （ 将字符串转成set默认顺序后，进行比较 ）
            many_tag = True
            case_list = dm.query_db()
            for case in case_list:
                if has_A:  # 精确匹配
                    if set(tag_name_list) == set(case["tag_name"].split(",")):
                        query_dict = {"_id": case["_id"]}
                        dm.update_db(query=query_dict, set_dict=set_dict, is_many=False)
                else:  # 模糊匹配
                    if set(tag_name_list) <= set(case["tag_name"].split(",")):
                        query_dict = {"_id": case["_id"]}
                        dm.update_db(query=query_dict, set_dict=set_dict, is_many=False)
    if not many_tag:
        dm.update_db(query=query_dict, set_dict=set_dict)
    return "设置成功"


def update_case_status(pro_name, _id):
    """
        更新项目测试用例状态(单个)
        < 关联接口 需要 同步更新 '上下线'状态 >
    """
    # 获取 需要修改的'上下线'状态
    dm = db_model(pro_name=pro_name, table=cfg.TABLE_CASE, error_msg="更新单个测试用例状态")
    query_dict = {"_id": ObjectId(_id)}
    case = dm.query_db(query=query_dict, filter={"_id": 0}, is_many=False)
    old_case_status = case.get("case_status")
    new_case_status = bool(1 - old_case_status)  # 布尔值取反
    # 获取当前用例的关联用例列表
    relation_list = get_relation_list_by_case_name(pro_name=pro_name, case_name=case.get("interface_name"))
    if relation_list:  # 存在关联用例，需要同步更新
        case_name_list = list(map(lambda case: case.get("interface_name"), relation_list))
        query_dict = {"interface_name": {"$in": case_name_list}}
    dm.update_db(query=query_dict, set_dict={"case_status": new_case_status})
    return new_case_status


def update_run_type_status(pro_name, run_type):
    """
    更新 运行方式 任务状态
    :param pro_name:
    :param run_type: cron | deploy
    :return:
    """
    dm = db_model(pro_name=pro_name, table=cfg.TABLE_CONFIG, error_msg="更新运行方式状态")
    query_dict = {"config_name": run_type}
    result = dm.query_db(query=query_dict, is_many=False)
    old_run_type_status = result.get("config_value")
    new_run_type_status = bool(1 - old_run_type_status)  # 布尔值取反
    dm.update_db(query=query_dict, set_dict={"config_value": new_run_type_status})
    return new_run_type_status


def case_import_action(pro_name, upload_file, import_method):
    """
    导入用例操作
    :param pro_name
    :param upload_file:
    :param import_method: 导入方式（批量新增、全部替换、批量新增+替换）
    :return:
    """
    res_info = dict()
    if upload_file is None:
        res_info["msg"] = u"上传文件不能为空"
    elif ('.' not in upload_file.filename) or \
            (upload_file.filename.rsplit('.', 1)[1] not in ['xls', 'xlsx', 'csv']):
        res_info["msg"] = u"格式仅支持：.xls、.xlsx、.csv"
    elif pro_is_running(pro_name):
        res_info["msg"] = u"当前项目正在运行中"
    else:
        # 将上传的文件保存入指定Excel文件中
        upload_file.save(cfg.UPLOAD_CASE_FILE)
        # 验证Excel用例格式
        verify_result, excel_list = verify_excel_and_transfer_format(cfg.UPLOAD_CASE_FILE)
        res_info["msg"] = verify_result
        if verify_result == "验证通过":
            res_info["msg"] = import_mongodb(pro_name, excel_list, import_method)
    return res_info


def import_mongodb(pro_name, excel_list, import_method):
    """
    将excel中的用例按照导入方式，导入mongo
    :param pro_name
    :param excel_list:
    :param import_method: 导入方式（batch_insert、all_replace、batch_insert_and_replace）
    :return:
    【 备 注 】
    all_replace：先清空数据库，然后全部插入
    batch_insert：区分'不在数据库中的用例列表'和'需要更新的用例列表'，只执行插入操作
    batch_insert_and_replace：区分'不在数据库中的用例列表'和'需要更新的用例列表'，分别执行插入和更新操作
    """
    with MongodbUtils(ip=cfg.MONGODB_ADDR, database=cfg.MONGODB_DATABASE, collection=pro_name + cfg.TABLE_CASE) as pro_db:
        try:

            if import_method == "all_replace":
                pro_db.drop()
                pro_db.insert_many(filled_other_field(excel_list))
                return "全部替换 操作成功 ！"
            else:
                # 获取数据库中的'接口名称列表'
                all_case_in_db = pro_db.find()
                interface_name_list_in_db = [case.get("interface_name") for case in all_case_in_db]
                # 区分'不在数据库中的用例列表'和'需要更新的用例列表'
                update_list = []
                insert_list = []
                for index, line_dict in enumerate(excel_list):
                    if line_dict["interface_name"] in interface_name_list_in_db:
                        update_list.append(line_dict)
                    else:
                        insert_list.append(line_dict)

                if import_method == "batch_insert":
                    # 插入新增的数据
                    if not is_null(insert_list):
                        pro_db.insert_many(filled_other_field(insert_list))
                    return "批量新增 操作成功 ！"
                else:  # batch_insert_and_replace
                    if not is_null(insert_list):
                        pro_db.insert_many(filled_other_field(insert_list))
                    # 更新数据
                    if not is_null(update_list):
                        for line_dict in update_list:
                            # line_dict["update_time"] = get_current_iso_date()
                            query_dict = {"interface_name": line_dict["interface_name"]}
                            update_dict = {"$set": line_dict}
                            pro_db.update(query_dict, update_dict)
                    return "批量新增+替换 操作成功 ！"
        except Exception as e:
            mongo_exception_send_DD(e=e, msg="导入'" + pro_name + "'项目excel测试用例数据")
            return "mongo error"


def filled_other_field(excel_list):
    """
    【 填补其他的字段 】
    :param excel_list:
    :return:
    """
    current_iso_date = get_current_iso_date()
    for index, line_dict in enumerate(excel_list):
        line_dict["response_info"] = ""
        line_dict["depend_field_value_list"] = []
        line_dict["actual_core_field_value_list"] = []
        line_dict["actual_field_name_list"] = []
        line_dict["downstream_relation_field_value_list"] = []
        line_dict["result_core_field_value"] = ""
        line_dict["result_field_name_list"] = ""
        line_dict["test_result"] = ""
        line_dict["run_status"] = False
        line_dict["update_time"] = current_iso_date
        line_dict["create_time"] = current_iso_date
    # show_excel_list(excel_list)
    return excel_list


def verify_excel_and_transfer_format(excel_file):
    """
    【 验 证 并 转 换 Excel 用 例 格 式 】
    1.获取用例字段名列表（去除前后空格）
    2.判断用例字段名是否正确 -> get_case_special_field_list()
    （1）是否有多余的字段
    （2）是否有缺失的字段
    3.转换'is_depend'字段格式
    （1）是否为依赖接口：is_depend
        问题：<Excel> 显示 FALSE、TRUE  <python> 显示 0、1 （ int类型 )
        解决：将 int 或 '其他形式字符串' 转换成 bool 类型（ 其他形式字符串：空、"false"、"true"、"False"、"True"等)
    4.检查'request_method'字段是否正确
    5.根据'is_depend'字段 检查必填项
    （1）若'is_depend=True：是依赖接口'
         1）验证依赖接口的必填项  get_not_null_field_list_with_depend()
         2）验证'depend_level'字段必须是 float 类型 or 数字型的字符串
    （2）若'is_depend=False：不是依赖接口'
         1）验证测试接口的必填项 get_not_null_field_list()
         2）验证'verify_mode'字段必须是 float 类型 or 数字型的字符串
           < 备注：<Excel> 显示 1、2  <python> 显示 1.0、2.0 （ float类型 ) 或 1、2 （str类型）>
    6.检查是否存在重复的用例
    （1）'接口名称'是否存在重复
    （2）'请求方式+接口地址'是否存在重复
    （3）'依赖等级'是否重复
    7.转换相关字段值的类型与格式
    （1）验证模式：verify_mode、depend_level
        问题：<Excel> 显示 1、2  <python> 显示 1.0、2.0 （ float类型 )
        解决：将 float 转换成 int 类型（ 若未填，则赋值 0  ）
    （2）用例状态：case_status
        问题：<Excel> 显示 FALSE、TRUE  <python> 显示 0、1 （ int类型 )
        解决：将 int 或 '其他形式字符串' 转换成 bool 类型（ 其他形式字符串：空、"false"、"true"、"False"、"True"等)
    （3）检查'请求头文件'和'请求参数'中是否存在中文逗号
    （4）将(以","分割)的相关字段值转换成list -> get_list_field()
        - 检查这些字段中是否存在中文逗号
        - < 里面的每一个元素的类型都是'str'（eg: "5"、"True") >
    （5）若存在'请求参数'，则需要检查是否以'?'或'{'开头
    8.检查'待比较关键字段名'列表与'期望的关键字段值'列表的数量是否一致
    """
    # 读取Excel用例文件（第一个工作表）
    excel_list = read_excel(filename=excel_file, sheet_index=0, set_head_row_num=1)
    # for line in excel_list:
    #     print(line)
    if excel_list == []:
        return "上传的excel文件中没有用例", None

    # 1.获取用例字段名列表（去除前后空格）
    case_field_list = list(excel_list[0].keys())

    # 2.判断用例字段名是否正确 ( 差集：另一集合中没有的部分 )
    if set(get_case_special_field_list()) - set(case_field_list):
        return "缺失相关的列", None
    if set(case_field_list) - set(get_case_special_field_list()):
        return "存在多余的列", None

    # 3.转换'is_depend'字段格式
    for index, line_dict in enumerate(excel_list):
        for key, value in line_dict.items():
            if key.strip() == "is_depend":
                if type(value) is int:
                    line_dict[key] = value == 1 or False
                else:
                    line_dict[key] = value.strip() in ["true", "True", "TRUE"] or False

    # 4.检查'request_method'字段是否正确
    for index, line_dict in enumerate(excel_list):
        for key, value in line_dict.items():
            if key.strip() == "request_method":
                if value.upper() in ["GET", "POST", "PUT", "DELETE"]:
                    line_dict[key] = value.upper()
                else:
                    return f"第 {str(index + 3)} 行的 < 请求方式 > 字段格式不正确", None

    # 5.根据'is_depend'字段 检查必填项
    for index, line_dict in enumerate(excel_list):
        if line_dict["is_depend"]:
            for key, value in line_dict.items():
                if key.strip() in get_not_null_field_list_with_depend() and str(value).strip() == "":
                    return f"第 {str(index + 3)} 行的 {key.strip()} 字段不能为空", None
                if key.strip() == "depend_level" and type(value) is not float and not value.isdigit():
                    return f"第 {str(index + 3)} 行的 < 依赖等级 > 字段格式不正确", None
        else:
            for key, value in line_dict.items():
                if key.strip() in get_not_null_field_list() and str(value).strip() == "":
                    return "第 " + str(index + 3) + " 行的 " + key.strip() + " 字段不能为空", None
                if key.strip() == "verify_mode" and type(value) is not float and not value.isdigit():
                    return f"第 {str(index + 3)} 行的 < 验证模式 > 字段格式不正确", None

    # 6.检查是否存在重复的用例(接口名称、请求方式+接口地址)
    interface_name_list = []  # '接口名称'列表
    method_and_url_list = []  # '请求方式+接口地址'列表
    depend_level_list = []    # '依赖等级'列表
    for line_dict in excel_list:
        interface_name_list.append(str(line_dict["interface_name"]).strip())
        method_and_url_list.append(str(line_dict["request_method"]).strip() + str(line_dict["interface_url"]).strip())
        if line_dict["is_depend"]:
            depend_level_list.append(str(int(line_dict["depend_level"])))

    interface_num_dict = {}  # 记录'接口名称'出现的次数 { "test_01": 1, "test_02": 2 }
    for interface_name in set(interface_name_list):
        interface_num_dict[interface_name] = interface_name_list.count(interface_name)
    for interface_name, num in interface_num_dict.items():
        if num > 1:
            return "interface_name = " + interface_name + " 字段重复出现了 " + str(num) + " 次", None

    # method_and_url_num_dict = {}  # 记录'请求方式+接口地址'出现的次数 { "GET/test/add": 1, "POST/test/update": 2 }
    # for method_and_url in set(method_and_url_list):
    #     method_and_url_num_dict[method_and_url] = method_and_url_list.count(method_and_url)
    # for method_and_url, num in method_and_url_num_dict.items():
    #     if num > 1:
    #         return "request_method + interface_url = " + method_and_url + " 的组合重复出现了 " + str(num) + " 次", None

    if depend_level_list:
        depend_level_num_dict = {}  # 记录'依赖等级'出现的次数 { "1": 1, "2": 2 }
        for depend_level in set(depend_level_list):
            depend_level_num_dict[depend_level] = depend_level_list.count(depend_level)
        for depend_level, num in depend_level_num_dict.items():
            if num > 1:
                return "depend_level = " + depend_level + " 字段重复出现了 " + str(num) + " 次", None

    # 7.转换字段值的类型与格式
    for index, line_dict in enumerate(excel_list):
        for key, value in line_dict.items():
            if key.strip() in ["verify_mode", "depend_level"]:
                if isinstance(value, float) or (isinstance(value, str) and value):
                    line_dict[key] = int(value)
                else:
                    line_dict[key] = 0
            if key.strip() == "case_status":
                if type(value) is int:
                    line_dict[key] = value == 1 or False
                else:
                    line_dict[key] = value.strip() in ["true", "True", "TRUE"] or False
            if key.strip() in ["request_header", "request_body"]:
                if "，" in str(value).strip():
                    return "第 " + str(index + 3) + " 行的 " + key.strip() + " 字段存在中文逗号 ！", None
                if "?" in value:
                    return "第 " + str(index + 3) + " 行的 " + key.strip() + " 字段值 不能包含 ? ", None

            if key.strip() in ["request_header"]:
                if value and not str(value).startswith("{"):
                    return "第 " + str(index + 3) + " 行的 " + key.strip() + " 字段值 必须以 { 开头 ！", None
                if "\"file\":" in line_dict.get("request_body") and ("Content-Type" in value or "content-type" in value):
                    return "第 " + str(index + 3) + " 行的 " + key.strip() + \
                           " 字段值 不能包含'Content-Type'，因为该用例是上传文件接口", None
                if value.count("'") % 2 != 0 or value.count("\"") % 2 != 0:
                    return "第 " + str(index + 3) + " 行的 " + key.strip() + " 字段值 引号配对有误", None

            if key.strip() in ["request_body"]:
                if str(value).startswith("{") and (value.count("'") % 2 != 0 or value.count("\"") % 2 != 0):
                    return "第 " + str(index + 3) + " 行的 " + key.strip() + " 字段值 引号配对有误", None

            if key.strip() in get_list_field():
                if str(value).strip() == "":
                    line_dict[key] = []
                else:
                    if "，" in str(value).strip():
                        return "第 " + str(index + 3) + " 行的 " + key.strip() + " 字段存在中文逗号 ！", None
                    else:
                        if type(value) is float:
                            line_dict[key] = [str(int(value))]
                        else:
                            line_dict[key] = str(str(value).strip()).split(",")

            if key.strip() == "request_params" and value:
                if not str(value).startswith("?"):
                    return "第 " + str(index + 3) + " 行的 " + key.strip() + " 字段值 必须以 ? 开头 ！", None

    # 8.检查'待比较关键字段名'列表与'期望的关键字段值'列表的数量是否一致'
    for index, line_dict in enumerate(excel_list):
        if len(line_dict["compare_core_field_name_list"]) != len(line_dict["expect_core_field_value_list"]):
            return "第 " + str(index + 3) + " 行的<待比较关键字段名列表>与<期望的关键字段值列表>字段数量不一致", None
        if line_dict["interface_name"] == line_dict["upstream_relation_case"]:
            return "第 " + str(index + 3) + " 行的<上游关联用例名称>不能与<当前接口名称>相同", None

    # show_excel_list(excel_list)
    return "验证通过", excel_list


def show_excel_list(excel_list):
    for index, line_dict in enumerate(excel_list):
        print("\n============== " + str(index) + " ================\n")
        for key, value in line_dict.items():
            print(key)
            print(value)
            print(type(value))
            print("--------")


def get_config_info(pro_name):
    """
    获取配置信息（ HOST 配置列表 | 全局变量配置列表 | 定时任务状态 ）
    :param pro_name:
    :return:
    """
    host_list = []
    global_variable_list = []
    cron_status = False
    deploy_status = False

    dm = db_model(pro_name=pro_name, table=cfg.TABLE_CONFIG, error_msg="获取配置信息列表")
    host_results_list = dm.query_db()
    for res in host_results_list:
        if res.get("config_type") == "host":
            host_dict = dict()
            host_dict["config_id"] = str(res.get("_id"))
            host_dict["host_name"] = str(res.get("config_name"))
            host_dict["host_url"] = str(res.get("config_value"))
            host_list.append(host_dict)
        elif res.get("config_type") == "global_variable":
            global_variable_dict = dict()
            global_variable_dict["config_id"] = str(res.get("_id"))
            global_variable_dict["global_variable_name"] = str(res.get("config_name"))
            global_variable_dict["global_variable_value"] = str(res.get("config_value"))
            global_variable_list.append(global_variable_dict)
        else:  # status
            if res.get("config_name") == "cron":
                cron_status = res.get("config_value")
            if res.get("config_name") == "deploy":
                deploy_status = res.get("config_value")
    return host_list, global_variable_list, cron_status, deploy_status


def get_config_info_for_result(pro_name, last_test_time):
    """
    从测试结果中获取配置信息 （ HOST | 全局变量 ）
    :return:
    """
    host = ""
    global_variable_dict = {}
    if not last_test_time:
        return host, global_variable_dict

    dm = db_model(pro_name=pro_name, table=cfg.TABLE_RESULT, error_msg="获取测试结果中的配置信息")
    # 获取最新测试时间的第一条用例
    case_dict = dm.query_db(query={"update_time": last_test_time})[0]
    host = case_dict.get("host")
    global_variable_dict = case_dict.get("global_variable_dict")
    return host, global_variable_dict


def get_host_url(pro_name, host_name):
    host_url = ""
    dm = db_model(pro_name=pro_name, table=cfg.TABLE_CONFIG, error_msg="获取host_url")
    host_dict = dm.query_db(query={"config_type": "host", "config_name": host_name}, is_many=False)
    if host_dict:
        host_url = host_dict.get("config_value", "")
    return host_url


def show_case_by_test(pro_name):
    """
    获取 测试用例列表 和 用例标签（ 测试页面 ）
    :param pro_name:
        < 备 注 >
        1.用例显示顺序：
        （1）依赖接口(上线、排序)
        （2）关联接口列表(上线、排序)
        （3）非关联接口列表(上线)
        （4）其他接口(下线)
        2.标签名称列表 tag_name_list
        （1）单个标签列表
        （2）组合标签列表
            归回,监控     表示 包含 '回归' 与 '监控' 标记
            归回,监控::A  表示 仅包含 '回归' 与 '监控' 标记
        3.页面显示的上次执行时间  exec_time
        （1）若 update_time - create_time > 0 则 exec_time = update_time
        （2）若 update_time - create_time = 0 则 exec_time = "--------"
    """
    dm = db_model(pro_name=pro_name, table=cfg.TABLE_CASE, error_msg="获取测试用例列表(_case)")
    case_list = dm.query_db()
    # 调整用例列表格式、获取单个和多个标签列表
    single_tag_list, multiple_tag_list = [], []
    for i in range(len(case_list)):
        case_list[i]["_id"] = str(case_list[i]["_id"])
        case_list[i]["exec_time"] = case_list[i]["update_time"] == case_list[i]["create_time"] and "--------" or \
                                    case_list[i]["update_time"]
        tag_name_list = case_list[i]["tag_name"].split(",")
        if tag_name_list[0] != "":
            single_tag_list.extend(tag_name_list)  # 获取单个标签列表 ["监控","回归"]
            if len(tag_name_list) > 1:
                multiple_tag_list.append(tag_name_list)  # 获取多个标签列表 [["监控","回归"],["监控","回归","部署"]]

    # 其他接口(下线)
    other_list_offline = list(filter(lambda x: not x["case_status"], case_list))
    # 依赖接口(上线、排序)
    depend_list_online = sorted(list(filter(lambda x: x["is_depend"] and x["case_status"], case_list)),
                                key=lambda keys: keys['depend_level'])
    # 非关联接口列表(上线)
    unrelation_list_online = list(filter(lambda x: x["case_status"] and not x["is_depend"] and
                                                   x["upstream_relation_case"] == "" and
                                                   x["downstream_relation_field_name_list"] == [], case_list))
    # 关联接口列表(上线、排序)
    relation_list_online = get_relation_list(pro_name=pro_name, list_type="all", case_list=case_list, filter_online=True)
    # 整合用例列表
    test_case_list = depend_list_online + relation_list_online + unrelation_list_online + other_list_offline
    return test_case_list, get_tag_name_list(single_tag_list, multiple_tag_list)


def show_case_by_report(pro_name, last_test_time=None):
    """
    获取 测试用例列表 和 用例标签（ 报告页面 ）
    :param pro_name:
    :param last_test_time: 最新一次的测试时间
        < 备 注 >
        1.用例显示顺序：
        （1）依赖接口(排序)
        （2）关联接口列表(排序)
        （3）非关联接口列表
        2.标签名称列表 tag_name_list
        （1）单个标签列表
        （2）组合标签列表
            归回,监控     表示 包含 '回归' 与 '监控' 标记
            归回,监控::A  表示 仅包含 '回归' 与 '监控' 标记
    """
    dm = db_model(pro_name=pro_name, table=cfg.TABLE_RESULT, error_msg="获取报告用例列表(_result)")
    query_dict = last_test_time and {"update_time": last_test_time} or {}
    case_list = dm.query_db(query=query_dict)

    # 调整用例列表格式、获取单个和多个标签列表
    single_tag_list, multiple_tag_list = [], []
    for i in range(len(case_list)):
        case_list[i]["_id"] = str(case_list[i]["_id"])
        case_list[i]["exec_time"] = case_list[i]["update_time"]
        tag_name_list = case_list[i]["tag_name"].split(",")
        if tag_name_list[0] != "":
            single_tag_list.extend(tag_name_list)  # 获取单个标签列表 ["监控","回归"]
            if len(tag_name_list) > 1:
                multiple_tag_list.append(tag_name_list)  # 获取多个标签列表 [["监控","回归"],["监控","回归","部署"]]

    # 依赖接口(排序)
    depend_list_online = sorted(list(filter(lambda x: x["is_depend"], case_list)),key=lambda keys: keys['depend_level'])
    # 非关联接口列表
    unrelation_list_online = list(filter(lambda x: not x["is_depend"] and x["upstream_relation_case"] == "" and
                                                   x["downstream_relation_field_name_list"] == [], case_list))
    # 关联接口列表(排序)
    relation_list_online = get_relation_list(pro_name=pro_name, list_type="test", case_list=case_list)

    # 整合用例列表
    test_case_list = depend_list_online + relation_list_online + unrelation_list_online
    return test_case_list, get_tag_name_list(single_tag_list, multiple_tag_list)


def get_tag_name_list(single_tag_list, multiple_tag_list):
    """
    获取整合后的 标签列表
    :param single_tag_list:  单标签列表
    :param multiple_tag_list: 多标签列表
    :return:
    """
    # 整理单个标签列表：去重  ["监控", "回归"]  ["监控::A", "回归::A"]
    single_tag_list = list(set(single_tag_list))
    single_tag_list_A = list(map(lambda x: x + "::A", single_tag_list))
    # 整理多个标签列表：合并相同列 ( 子列表排序 -> 装换格式 -> 去重 )
    # [["监控","回归"],["回归","监控"],["回归","监控","部署"]] -> ["监控,回归","回归,监控,部署"] ["监控,回归::A","回归,监控,部署::A"]
    multiple_tag_list = list(set(map(lambda x: ','.join(x), map(lambda tag_list: sorted(tag_list), multiple_tag_list))))
    multiple_tag_list_A = list(map(lambda x: x + "::A", multiple_tag_list))
    # 整合标签列表
    tag_name_list = single_tag_list + multiple_tag_list + single_tag_list_A + multiple_tag_list_A
    return tag_name_list


def get_test_time_list(pro_name):
    """
    获取 测试执行时间 列表（倒序排列）
    :param pro_name:
    :return:
    """
    pipline = []
    # 分组查询 ( _id 为必填的分组字段 )
    pipline.append({"$group": {"_id": "$update_time", "case_num": {"$sum": 1}, "更新时间": {"$first": "$update_time"}}})
    pipline.append({"$sort": {"更新时间": -1}})
    dm = db_model(pro_name=pro_name, table=cfg.TABLE_RESULT, error_msg="获取测试时间列表")
    result_list = dm.pip_aggregate(pipline=pipline)
    return list(map(lambda x: x.get("更新时间"), result_list))


def screen_test_time_by_run_type(pro_name, run_type):
    """
    通过'运行方式' 筛选出对应 '测试时间'列表
    :param pro_name
    :param run_type: all | cron | manual | deploy
    :return:
    """
    pipline = []
    # 筛选条件
    if run_type != "all":
        pipline.append({"$match": {"run_type": run_type}})
    # 分组查询 ( _id 为必填的分组字段 )
    pipline.append({"$group": {"_id": "$update_time", "case_num": {"$sum": 1}, "更新时间": {"$first": "$update_time"}}})
    pipline.append({"$sort": {"更新时间": -1}})
    dm = db_model(pro_name=pro_name, table=cfg.TABLE_RESULT, error_msg="获取指定运行方式的测试时间列表")
    result_list = dm.pip_aggregate(pipline=pipline)
    return list(map(lambda x: str(x.get("更新时间")), result_list))


def screen_tag_name_by_test_time(pro_name, test_time):
    """
    通过'测试时间' 筛选 '标签名称'列表
    :param pro_name
    :param test_time: 2021-08-27 20:59:54
    :return:
    """
    # 将测试时间转换成iso格式
    ISODate = parser.parse(test_time)
    dm = db_model(pro_name=pro_name, table=cfg.TABLE_RESULT, error_msg="获取指定测试时间的标签名称列表")
    case_list = dm.query_db(query={"update_time": ISODate})
    single_tag_list = []
    multiple_tag_list = []
    for case in case_list:
        tag_name_list = case.get("tag_name").split(",")
        if tag_name_list[0] != "":
            single_tag_list.extend(tag_name_list)  # 获取单个标签列表 ["监控","回归"]
            if len(tag_name_list) > 1:
                multiple_tag_list.append(tag_name_list)  # 获取多个标签列表 [["监控","回归"],["监控","回归","部署"]]

    # 整理单个标签列表：去重  ["监控", "回归"]  ["监控::A", "回归::A"]
    single_tag_list = list(set(single_tag_list))
    single_tag_list_A = list(map(lambda x: x + "::A", single_tag_list))

    # 整理多个标签列表：合并相同列 ( 子列表排序 -> 装换格式 -> 去重 )
    # [["监控","回归"],["回归","监控"],["回归","监控","部署"]] -> ["监控,回归","回归,监控,部署"] ["监控,回归::A","回归,监控,部署::A"]
    multiple_tag_list = list(set(map(lambda x: ','.join(x), map(lambda tag_list: sorted(tag_list), multiple_tag_list))))
    multiple_tag_list_A = list(map(lambda x: x + "::A", multiple_tag_list))

    # 整合标签列表
    tag_name_list = single_tag_list + multiple_tag_list + single_tag_list_A + multiple_tag_list_A
    return tag_name_list


def get_statist_data_for_case(pro_name):
    """
    获取用例统计数据（ _case 表 ）
    （1）总计：依赖、测试
    （2）最新测试结果：成功、失败、错误
        < 注：根据最新测试时间进行统计，排除依赖接口 >
    :param pro_name:
    :return:
    """
    statist_data = {"depend": 0, "test": 0, "success": 0, "fail": 0, "error": 0}

    dm = db_model(pro_name=pro_name, table=cfg.TABLE_CASE, error_msg="获取用例统计数据_case表")
    case_list = dm.query_db()
    statist_data["depend"] = len(list(filter(lambda x: x.get("is_depend"), case_list)))
    statist_data["test"] = len(list(filter(lambda x: not x.get("is_depend"), case_list)))

    # 按照"update_time"降序排列后，取第一条记录的"update_time"值
    # new_update_time = pro_db.find().sort("update_time", -1)[0].get("update_time")

    pipline = []
    # 筛选条件
    # pipline.append({"$match": {"is_depend": False, "update_time": {"$in": [new_update_time]}}})
    pipline.append({"$match": {"is_depend": False}})  # 排除依赖接口
    # 分组查询 ( _id 为必填的分组字段 )
    pipline.append({"$group": {"_id": "$update_time", "case_num": {"$sum": 1}, "更新时间": {"$first": "$update_time"}}})
    pipline.append({"$sort": {"更新时间": -1}})
    pipline.append({"$limit": 1})
    # 连表查询
    pipline.append(
        {"$lookup": {"from": pro_name + "_case", "localField": "更新时间", "foreignField": "update_time", "as": "case_db"}})
    # 拆分行（ 将连表后 新表数据列表，拆分成行 ）
    pipline.append({"$unwind": "$case_db"})
    pipline.append({"$match": {"case_db.is_depend": False}})  # 排除依赖接口
    # 投影字段
    pipline.append(
        {"$project": {"_id": 0, "更新时间": "$更新时间", "接口名称": "$case_db.interface_name", "测试结果": "$case_db.test_result"}})
    statist_result = dm.pip_aggregate(pipline=pipline)

    for data in statist_result:
        res = data.get("测试结果")
        statist_data["success"] = "success" in res and statist_data["success"] + 1 or statist_data["success"]
        statist_data["error"] = "error" in res and statist_data["error"] + 1 or statist_data["error"]
        if "fail" in res:
            if "依赖接口" in res:
                statist_data["error"] = statist_data["error"] + 1
            else:
                statist_data["fail"] = statist_data["fail"] + 1
    return statist_data

    # with MongodbUtils(ip=cfg.MONGODB_ADDR, database=cfg.MONGODB_DATABASE, collection=pro_name + cfg.TABLE_CASE) as pro_db:
    #     try:
    #         depend_cursor = pro_db.find({"is_depend": True})
    #         test_cursor = pro_db.find({"is_depend": False})
    #         statist_data["depend"] = len(list(depend_cursor))
    #         statist_data["test"] = len(list(test_cursor))
    #
    #         # 按照"update_time"降序排列后，取第一条记录的"update_time"值
    #         # new_update_time = pro_db.find().sort("update_time", -1)[0].get("update_time")
    #
    #         pipline = []
    #         # 筛选条件
    #         # pipline.append({"$match": {"is_depend": False, "update_time": {"$in": [new_update_time]}}})
    #         pipline.append({"$match": {"is_depend": False}})  # 排除依赖接口
    #         # 分组查询 ( _id 为必填的分组字段 )
    #         pipline.append({"$group": {"_id": "$update_time", "case_num": {"$sum": 1}, "更新时间": {"$first": "$update_time"}}})
    #         pipline.append({"$sort": {"更新时间": -1}})
    #         pipline.append({"$limit": 1})
    #         # 连表查询
    #         pipline.append({"$lookup": {"from": pro_name + "_case", "localField": "更新时间", "foreignField": "update_time", "as": "case_db"}})
    #         # 拆分行（ 将连表后 新表数据列表，拆分成行 ）
    #         pipline.append({"$unwind": "$case_db"})
    #         pipline.append({"$match": {"case_db.is_depend": False}})  # 排除依赖接口
    #         # 投影字段
    #         pipline.append({"$project": {"_id": 0, "更新时间": "$更新时间", "接口名称": "$case_db.interface_name", "测试结果": "$case_db.test_result"}})
    #         statist_result = pro_db.aggregate(pipline, allowDiskUse=True)
    #
    #         for index, data in enumerate(statist_result):
    #             # print(data)
    #             res = data.get("测试结果")
    #             statist_data["success"] = "success" in res and statist_data["success"] + 1 or statist_data["success"]
    #             statist_data["fail"] = "fail" in res and statist_data["fail"] + 1 or statist_data["fail"]
    #             statist_data["error"] = "error" in res and statist_data["error"] + 1 or statist_data["error"]
    #     except Exception as e:
    #         mongo_exception_send_DD(e=e, msg="获取'" + pro_name + "'项目统计数据(" + cfg.TABLE_CASE + ")")
    #     finally:
    #         return statist_data


def get_statist_data_for_result(pro_name, test_time=None):
    """
    获取用例统计数据（ _result ）
    （1）总计：依赖、测试
    （2）当前测试结果：成功、失败、错误
         报告页面 - 默认显示最新一次测试时间的用例
         报告搜索 - 根据更新时间搜索
    :param pro_name:
    :return:
            柱状图 - 柱体 （ value：响应时间、color：参照测试结果 ）
            data: [{value: 200, itemStyle: {color: '#749f83'}},
                   {value: 1000, itemStyle: {color: '#ca8622'}},
                   {value: 100, itemStyle: {color: '#749f83'}},
                   {value: 7, itemStyle: {color: '#749f83'}},
                   {value: 3000, itemStyle: {color: '#c23531'}},
                   {value: 178, itemStyle: {color: '#749f83'}},
                   {value: 670, itemStyle: {color: '#749f83'}}]

            柱状图 - 气泡 （ value：响应时间、xAxis：用例索引0开始、yAxis: 响应时间、color：保持不变 ）
            data: [ {value: 200, xAxis: 0, yAxis: 200, itemStyle: {color: '#7E3D76'}},
                    {value: 1000, xAxis: 1, yAxis: 1000, itemStyle: {color: '#7E3D76'}},
                    {value: 100, xAxis: 2, yAxis: 100, itemStyle: {color: '#7E3D76'}},
                    {value: 7, xAxis: 3, yAxis: 7, itemStyle: {color: '#7E3D76'}} ]

    """
    statist_data = {"depend": 0, "test": 0, "success": 0, "fail": 0, "error": 0}  # 统计结果
    interface_name_list = []  # 柱状图 - 接口名称列表
    bubble_list = []  # 柱状图 - 气泡列表（ 响应时间、列表索引、颜色不变 ）
    http_rt_list = []  # 柱状图 - 响应时间列表 ( 颜色参照测试结果 )

    # 转换 mongodb 时间格式
    test_time = (test_time and isinstance(test_time, str)) and parser.parse(test_time) or test_time
    dm = db_model(pro_name=pro_name, table=cfg.TABLE_RESULT, error_msg="获取用例统计数据_result")
    case_list = dm.query_db(query={"update_time": test_time})
    depend_list = list(filter(lambda x: x.get("is_depend"), case_list))
    test_list = list(filter(lambda x: not x.get("is_depend"), case_list))
    statist_data["depend"] = len(depend_list)
    for index, data in enumerate(test_list):
        test_result = data.get("test_result")
        interface_name = data.get("interface_name")
        rt = data.get("http_rt")
        interface_name_list.append(interface_name)
        bubble_list.append({"value": rt, "xAxis": index, "yAxis": rt, "itemStyle": {"color": "#7E3D76"}})
        statist_data["test"] += 1
        if "success" in test_result:
            statist_data["success"] += 1
            http_rt_list.append({"value": rt, "itemStyle": {"color": "#749f83"}})
        elif "error" in test_result:
            statist_data["error"] += 1
            http_rt_list.append({"value": rt, "itemStyle": {"color": "#ca8622"}})
        else:  # "fail"
            if "依赖接口" in test_result:
                statist_data["error"] = statist_data["error"] + 1
                http_rt_list.append({"value": rt, "itemStyle": {"color": "#ca8622"}})
            else:
                statist_data["fail"] = statist_data["fail"] + 1
                http_rt_list.append({"value": rt, "itemStyle": {"color": "#c23531"}})
    return statist_data, interface_name_list, http_rt_list, bubble_list


def get_case_search_result(request_args, pro_name, table_tag):
    """
    获取用例搜索结果
    :param request_args: GET请求参数
    :param pro_name:
    :param table_tag:  _case | _result
    :return:
     【 搜 索 用 例 的 排 序 】
     1.上线的排在前面
     2.依赖接口排在前面
     3.依赖等级小的排在前面

     < 备 注 > 若搜索的是 _result 表，则还需要获取 host、global_variable_dict 等字段 供页面显示

     【 标签名称 筛选规则举例 】（ 查询获取数据后，再进行筛选 ）
     1.匹配未打标签的 (::N)
     2.匹配一个标签的
     （1）模糊匹配 (回归)     匹配所有包含该标签的
     （2）精确匹配 (回归::A)  匹配只包含该标签的
     3.匹配多个标签的
     （1）模糊匹配 (回归,监控)    匹配所有包含该标签的（不考虑顺序）
     （2）精确匹配 (回归,监控::A) 匹配只包含该标签的（不考虑顺序）
    """
    search_pattern = {}
    interface_name = request_args.get("interface_name", "").strip()
    interface_url = request_args.get("interface_url", "").strip()
    request_method = request_args.get("request_method", "").strip()
    case_status = request_args.get("case_status", "").strip()
    test_result = request_args.get("test_result", "").strip()
    case_type = request_args.get("case_type", "").strip()  # 依赖 depend、测试 test、关联 relation
    relate_run_time = request_args.get("relate_run_time", "").strip()
    test_time = request_args.get("test_time", "").strip()
    tag_name = request_args.get("tag_name", "").strip()
    if interface_name:
        search_pattern["interface_name"] = re.compile(interface_name)  # 模糊搜索
    if interface_url:
        search_pattern["interface_url"] = re.compile(interface_url)  # 模糊搜索
    if test_result:
        search_pattern["test_result"] = re.compile(test_result)  # 模糊搜索
    if request_method:
        search_pattern["request_method"] = request_method  # 精确搜索
    if case_status:
        search_pattern["case_status"] = case_status in ["true", "TRUE", "True"] and True or False
    if case_type:
        if case_type == "depend":
            search_pattern["is_depend"] = True
        elif case_type == "test":
            search_pattern["is_depend"] = False
        else:  # relation ( 条件：上游用例 或 下游关联字段 不能同时为空 )  查询非空 -> {"$ne": ""}
            search_pattern["$or"] = [{"upstream_relation_case": {"$ne": ""}}, {"downstream_relation_field_name_list": {"$ne": []}}]

    dm = db_model(pro_name=pro_name, table=table_tag, error_msg="获取用例搜索结果")
    if table_tag == "_case":
        # 判断 是否需要将最新执行时间加入搜索条件
        relate_run_time = relate_run_time in ["true", "TRUE", "True"] and True or False
        if relate_run_time:
            search_pattern["update_time"] = dm.query_db(is_cursor=True).sort("update_time", -1)[0].get("update_time")
    else:  # "_result"
        test_time = test_time and parser.parse(test_time) or test_time
        search_pattern["update_time"] = test_time

    results = dm.query_db(query=search_pattern)
    # 对搜索到的数据，根据'标签名称'进行筛选
    tag_filter_results = []
    if results and tag_name:
        for case_dict in results:
            if tag_name == "no_tag" and case_dict.get("tag_name") == "":  # 匹配'无标签'的
                tag_filter_results.append(case_dict)
            else:
                case_tag_name_list = case_dict.get("tag_name").split(",")
                tmp = tag_name.split("::")
                search_tag_name_list = tmp[0].split(",")
                if len(tmp) == 1:  # < 模糊匹配 >
                    if set(search_tag_name_list) <= set(case_tag_name_list):
                        tag_filter_results.append(case_dict)
                else:  # < 精准匹配 >
                    if tmp[1] == "A":
                        if set(search_tag_name_list) == set(case_tag_name_list):
                            tag_filter_results.append(case_dict)
    else:
        tag_filter_results = results

    test_case_list = []
    other_list_offline = []  # 其他接口(下线)
    depend_list_online = []  # 依赖接口(上线、排序)
    test_list_online = []  # 测试接口(上线)
    if tag_filter_results:
        # 对搜索到的数据 进行格式处理
        for res in tag_filter_results:
            test_case_dict = dict()
            test_case_dict["_id"] = str(res.get("_id"))
            test_case_dict["tag_name"] = res.get("tag_name")
            test_case_dict["interface_name"] = res.get("interface_name")
            test_case_dict["interface_url"] = res.get("interface_url")
            test_case_dict["request_method"] = res.get("request_method")
            test_case_dict["request_header"] = str(res.get("request_header")).replace('"', "'")
            test_case_dict["request_params"] = str(res.get("request_params")).replace('"', "'")
            test_case_dict["request_body"] = str(res.get("request_body")).replace('"', "'")
            test_case_dict["compare_core_field_name_list"] = str(res.get("compare_core_field_name_list"))
            test_case_dict["expect_core_field_value_list"] = str(res.get("expect_core_field_value_list"))
            test_case_dict["expect_field_name_list"] = str(res.get("expect_field_name_list"))
            test_case_dict["verify_mode"] = res.get("verify_mode")
            test_case_dict["upstream_relation_case"] = res.get("upstream_relation_case")
            test_case_dict["downstream_relation_field_name_list"] = res.get("downstream_relation_field_name_list")  # 列表类型
            test_case_dict["downstream_relation_field_value_list"] = str(res.get("downstream_relation_field_value_list"))
            test_case_dict["is_depend"] = res.get("is_depend")
            test_case_dict["depend_level"] = res.get("depend_level")
            test_case_dict["depend_field_name_list"] = str(res.get("depend_field_name_list"))
            test_case_dict["depend_field_value_list"] = str(res.get("depend_field_value_list"))
            test_case_dict["actual_core_field_value_list"] = str(res.get("actual_core_field_value_list"))
            test_case_dict["actual_field_name_list"] = str(res.get("actual_field_name_list"))
            test_case_dict["case_status"] = res.get("case_status")
            test_case_dict["exec_time"] = res.get("update_time") == res.get("create_time") and "--------" or str(res.get("update_time"))
            test_case_dict["test_result"] = res.get("test_result")
            # 若搜索的是 _result 表，则还需要获取 host、global_variable_dict 等字段 供页面显示
            if table_tag == "_result":
                test_case_dict["host"] = res.get("host", "")
                test_case_dict["global_variable_dict"] = res.get("global_variable_dict", "")
            # 区分 用例
            if test_case_dict["case_status"]:  # 上线
                if test_case_dict["is_depend"]:  # 依赖(上线)
                    depend_list_online.append(test_case_dict)
                else:  # 测试(上线)
                    test_list_online.append(test_case_dict)
            else:  # 下线
                other_list_offline.append(test_case_dict)

        # 依赖接口(上线、排序)
        depend_list_online = sorted(depend_list_online, key=lambda keys: keys['depend_level'])
        # 非关联接口列表(上线)
        unrelation_list_online = list(filter(lambda x: x["upstream_relation_case"] == "" and
                                                       x["downstream_relation_field_name_list"] == [],
                                             test_list_online))
        # 关联接口列表(上线、排序)
        relation_list_online = get_relation_list(pro_name=pro_name, list_type="test", case_list=test_list_online)
        # 整合用例列表
        test_case_list = depend_list_online + relation_list_online + unrelation_list_online + other_list_offline
    return test_case_list, len(test_case_list)


def get_config_result(request_json, pro_name, config_type):
    """
    配置 HOST 或 全局变量 （ 添加 | 编辑 ）
    :param request_json:
    :param pro_name:
    :param config_type:  host | global_variable
    :return:

        < 逻 辑 >
        判断 host_name 是否存在
        1.若存在，则更新
        2.若不存在，则新增
    """
    # 获取请求中的参数
    config_name = request_json.get("config_name", "").strip()
    config_value = request_json.get("config_value", "").strip()

    # 若项目在运行中，不能进行配置
    if pro_is_running(pro_name):
        return "当前项目正在运行中"

    # 检查必填项
    if is_null(config_name) or is_null(config_value):
        return "必填项 不能为空"

    # 若配置host，则需要检查 host_url
    if config_type == "host" and config_value[-1] == "/":
        return "host_url 最后不能带有 /"

    dm = db_model(pro_name=pro_name, table=cfg.TABLE_CONFIG, error_msg="配置HOST或全局变量")
    config_data = dm.query_db(query={"config_type": config_type, "config_name": config_name}, is_many=False)
    if config_data:
        dm.update_db(query={"config_name": config_name}, set_dict={"config_value": config_value}, is_many=False)
        return "配置 更新成功 !"
    else:
        dm.insert_db(data={"config_type": config_type, "config_name": config_name, "config_value": config_value}, is_many=False)
        return "配置 新增成功 !"


def get_check_depend_variable_result(pro_name):
    """
    检查依赖变量配置是否正确
    < 步 骤 >
    1.获取所有上线的"依赖接口列表"和"测试接口列表"
    2.获取'依赖接口列表'中的'依赖字段名列表'（去重、排序）
    3.获取'测试接口列表'中的'依赖字段名字典' { "接口名称"：[依赖字段名列表] }
    4.判断 是否使用了依赖变量
    5.判断'测试接口列表'中的依赖字段是否都包含在'依赖接口列表'中的依赖字段里面
    """
    # 1.获取所有上线的"依赖接口列表"和"测试接口列表"
    dm = db_model(pro_name=pro_name, table=cfg.TABLE_CASE, error_msg="检查依赖变量配置是否正确")
    interface_list = dm.query_db(query={"case_status": True})
    depend_interface_list = list(filter(lambda x: x.get("is_depend"), interface_list))
    test_interface_list = list(filter(lambda x: not x.get("is_depend"), interface_list))

    # 2.获取'依赖接口列表'中的'依赖字段名列表'（去重、排序）
    depend_field_list = []
    for index, depend_interface_dict in enumerate(depend_interface_list):
        depend_field_list += depend_interface_dict["depend_field_name_list"]
    depend_field_list = list(set(depend_field_list))
    depend_field_list.sort()

    # 3.获取'测试接口列表'中的'依赖字段名字典' { "接口名称"：[依赖字段名列表] }
    test_depend_field_dict = {}
    for index, test_interface_dict in enumerate(test_interface_list):
        params_depend_field_list = []
        for key, value in test_interface_dict.items():
            if key in ["interface_url", "request_header", "request_params"]:
                params_depend_field_list.extend(capture_str_variable(str=value, v_tag="{{"))
        if params_depend_field_list:
            test_depend_field_dict[test_interface_dict.get("interface_name")] = params_depend_field_list

    log.info("\n")
    log.info("depend_field_list  ->  " + str(depend_field_list))
    log.info("test_depend_field_dict  ->  " + str(test_depend_field_dict))
    log.info("\n")

    # 4.判断 是否使用了依赖变量
    if not test_depend_field_dict:
        return "未使用依赖变量"
    else:  # 5.判断'测试接口列表'中的依赖字段是否都包含在'依赖接口列表'中的依赖字段里面
        for interface_name, field_list in test_depend_field_dict.items():
            no_contain_list = [field for field in field_list if field not in depend_field_list]
            if no_contain_list:
                return "'" + interface_name + "' 接口含有的依赖变量 " + str(no_contain_list) + " 没有配置"
        return "检查通过"


def get_check_global_variable_result(pro_name):
    """
    检查全局变量配置是否正确：
    < 步 骤 >
    1.获取所有上线的"接口列表"
    2.获取"接口列表"中带有<>标记的"全局变量字典" { "接口名称"：[全局变量字段名列表] }
    3.获取数据库中已经配置的'global_variable'字典
    4.判断 是否使用了未配置的全局变量
    """
    # 1.获取所有上线的"接口列表"
    dm = db_model(pro_name=pro_name, table=cfg.TABLE_CASE, error_msg="获取上线的用例列表")
    interface_list = dm.query_db(query={"case_status": True})

    # 2.获取"接口列表"中带有<>标记的"全局变量字典" { "接口名称"：[全局变量字段名列表] }
    interface_variable_dict = {}
    for interface_dict in interface_list:
        variable_field_list = []
        for key, value in interface_dict.items():
            if key in ["interface_url", "request_header", "request_params", "request_body"]:
                variable_field_list.extend(capture_str_variable(str=value, v_tag="<"))
        if variable_field_list:
            interface_variable_dict[interface_dict.get("interface_name")] = variable_field_list

    # 3.获取数据库中已经配置的'global_variable'字典
    global_variable_dict = {}
    dm = db_model(pro_name=pro_name, table=cfg.TABLE_CONFIG, error_msg="获取全局变量_config")
    results_list = dm.query_db(query={"config_type": "global_variable"})
    for res in results_list:
        global_variable_dict[res.get("config_name")] = res.get("config_value")

    log.info("\n")
    log.info("global_variable_dict  ->  " + str(global_variable_dict))
    log.info("interface_variable_dict  ->  " + str(interface_variable_dict))
    log.info("\n")

    # 判断是否使用了全局变量
    msg = "检查通过"
    if not interface_variable_dict:
        msg = "未使用全局变量"
    else:
        # 4.判断 是否使用了未配置的全局变量
        for interface_name, variable_list in interface_variable_dict.items():
            for variable in variable_list:
                if variable not in global_variable_dict.keys():
                    msg = "'" + interface_name + "' 接口含有的全局变量 " + variable + " 没有配置"

    return msg, global_variable_dict


def get_case_operation_result(request_json, pro_name, mode):
    """
    获取用例添加结果
    :param request_json:
    :param pro_name:
    :param mode:  添加 add | 编辑 edit
    :return:
    【 添 加 步 骤 】
    0.若项目在运行中，不能进行编辑
    1.验证'is_depend'字段不能为空
    2.转换'is_depend'字段格式 string -> bool
    3.根据'is_depend'字段 检查必填项
    4.检查需要转list的字段中是否存在中文的逗号
    5.若存在'请求参数'，则需要检查是否以'?'开头
    6.相关字段的格式转换
    （1）'用例状态' string -> bool
    （2）'列表字段' string -> list
    （3）'验证模式、依赖等级' string -> int （ 若为空，则赋值 0 ）
    7.检查'待比较关键字段名'列表与'期望的关键字段值'列表的数量是否一致
    8.整合公共的用例字段
    9.验证数据库中是否已存在
    （1）'接口名称'
    （2）'请求方式+接口地址'
    （3）'依赖等级' （若是依赖接口）
        （ 注意：若为'编辑'模式，则先排除编辑自身后，在进行上述判断 ）
    10.'新增'或'更新'用例数据

    【 字 段 格 式 】
    01.标签名称：tag_name
    02.接口名称：interface_name（ 必填 ）
    03.接口地址：interface_url（ 必填 ）
    04.请求方式：request_method（ 必填 ）
    05.请求头文件：request_header
    06.请求参数：request_params
    07.请求正文体：request_body
    08.验证模式：verify_mode（ 次必填 ）                            < (表单)string -> (Mongo)int >
    09.待比较关键字段名列表：compare_core_field_name_list（ 次必填 ） < (表单)string -> (Mongo)list >（以","分割）
    10.期望的关键字段值列表：expect_core_field_value_list（ 次必填 ） < (表单)string -> (Mongo)list >（以","分割）
    11.期望的响应字段列表：expect_field_name_list                   < (表单)string -> (Mongo)list >（以","分割）
    12.上游关联用例：upstream_relation_case
    13.下游关联字段名列表：downstream_relation_field_name_list     < (表单)string -> (Mongo)list >（以","分割）
    14.是否为依赖接口：is_depend  （ 必填 ）                        < (表单)string -> (Mongo)bool >
    15.依赖等级：depend_level    （ 次必填 ）                      < (表单)string  -> (Mongo)int >
    16.依赖字段名列表：depend_field_name_list                     < (表单)string -> (Mongo)list >（以","分割）
    17.用例状态：case_status                                     < (表单)string -> (Mongo)bool >
    """
    # 获取请求中的参数
    _id = request_json.get("_id", "").strip()
    tag_name = request_json.get("tag_name", "").strip()
    interface_name = request_json.get("interface_name", "").strip()
    interface_url = request_json.get("interface_url", "").strip()
    request_method = request_json.get("request_method", "").strip()
    request_header = request_json.get("request_header", "").strip()
    request_params = request_json.get("request_params", "").strip()
    request_body = request_json.get("request_body", "").strip()
    verify_mode = request_json.get("verify_mode", "").strip()
    compare_core_field_name_list = request_json.get("compare_core_field_name_list", "").strip()
    expect_core_field_value_list = request_json.get("expect_core_field_value_list", "").strip()
    expect_field_name_list = request_json.get("expect_field_name_list", "").strip()
    upstream_relation_case = request_json.get("upstream_relation_case", "").strip()
    downstream_relation_field_name_list = request_json.get("downstream_relation_field_name_list", "").strip()
    is_depend = request_json.get("is_depend", "").strip()
    depend_level = request_json.get("depend_level", "").strip()
    depend_field_name_list = request_json.get("depend_field_name_list", "").strip()
    case_status = request_json.get("case_status", "").strip()

    # 0.若项目在运行中，不能进行编辑
    if pro_is_running(pro_name) and mode == "edit":
        return "当前项目正在运行中"

    # 1.验证'is_depend'字段不能为空
    if is_depend == "":
        return "<是否为依赖接口> 未选择"

    # 2.转换'is_depend'字段格式 string -> bool
    is_depend = is_depend in ["true", "TRUE", "True"]

    # 3.根据'is_depend'字段 检查必填项
    if is_null(interface_name) or is_null(interface_url) or is_null(request_method):
        return "必填项 不能为空"
    if is_depend:
        if is_null(depend_level) or is_null(depend_field_name_list):
            return "依赖必填项 不能为空"
    else:
        if is_null(verify_mode) or is_null(compare_core_field_name_list) or is_null(expect_core_field_value_list):
            return "测试必填项 不能为空"

    # 4.检查需要转list的字段中是否存在中文的逗号
    for each in [request_header, request_body, compare_core_field_name_list, expect_core_field_value_list,
                 expect_field_name_list, depend_field_name_list, tag_name, downstream_relation_field_name_list]:
        if "，" in each:
            return "相关列表字段中 存在中文逗号 ！"

    # 5.若存在'请求参数'，则需要检查是否以'?'开头
    if request_params and not request_params.startswith("?"):
        return "'请求参数' 必须以 ? 开头 ！"

    # 6.若存在'请求头文件'，则需要检查
    if request_header:
        if not request_header.startswith("{"):
            return "'请求头文件' 必须以 { 开头 ！"
        if "\"file\":" in request_body and ("Content-Type" in request_header or "content-type" in request_header):
            return "上传文件接口  请求头中不能含有'Content-Type'字段"
        if request_header.count("'") % 2 != 0 or request_header.count("\"") % 2 != 0:
            return "'请求头文件' 引号配对有误"

    # 7.若存在'请求正文体'，则需要检查
    if request_body:
        if "?" in request_body:
            return "'请求正文体' 不能包含 ? "
        if request_body.startswith("{") and (request_body.count("'") % 2 != 0 or request_body.count("\"") % 2 != 0):
            return "'请求正文体' 引号配对有误"

    # 8.相关字段的格式转换
    case_status = case_status in ["true", "TRUE", "True"]
    verify_mode = verify_mode != "" and int(verify_mode) or 0
    depend_level = depend_level != "" and int(depend_level) or 0
    # 若为空则赋值[],否则赋值['aa','bb']
    compare_core_field_name_list = compare_core_field_name_list != "" and str(compare_core_field_name_list.strip()).split(",") or []
    expect_core_field_value_list = expect_core_field_value_list != "" and str(expect_core_field_value_list.strip()).split(",") or []
    expect_field_name_list = expect_field_name_list != "" and str(expect_field_name_list.strip()).split(",") or []
    depend_field_name_list = depend_field_name_list != "" and str(depend_field_name_list.strip()).split(",") or []
    downstream_relation_field_name_list = downstream_relation_field_name_list != "" and str(downstream_relation_field_name_list.strip()).split(",") or []

    # 9.检查'待比较关键字段名'列表与'期望的关键字段值'列表的数量是否一致
    if len(compare_core_field_name_list) != len(expect_core_field_value_list):
        return "<待比较的关键字段名> 与 <期望的关键字段值> 数量必须一致"

    # 10.检查'接口名称'与'关联用例'不能相同
    if interface_name == upstream_relation_case:
        return "<上游关联用例名称> 不能与 <当前接口名称> 相同"

    # 11.整合公共的用例字段
    current_iso_date = get_current_iso_date()
    test_case_dict = {"tag_name": tag_name, "interface_name": interface_name, "interface_url": interface_url,
                      "request_method": request_method, "request_header": request_header,
                      "request_params": request_params, "request_body": request_body, "verify_mode": verify_mode,
                      "compare_core_field_name_list": compare_core_field_name_list,
                      "expect_core_field_value_list": expect_core_field_value_list,
                      "expect_field_name_list": expect_field_name_list, "upstream_relation_case": upstream_relation_case,
                      "downstream_relation_field_name_list": downstream_relation_field_name_list, "is_depend": is_depend,
                      "depend_level": depend_level, "depend_field_name_list": depend_field_name_list, "case_status": case_status}

    with MongodbUtils(ip=cfg.MONGODB_ADDR, database=cfg.MONGODB_DATABASE, collection=pro_name + cfg.TABLE_CASE) as pro_db:
        try:
            if mode == "edit":
                # 12.验证数据库中是否已存在
                old_case_dict = pro_db.find_one({"_id": ObjectId(_id)})
                old_interface_name = old_case_dict.get("interface_name")
                old_request_method = old_case_dict.get("request_method")
                old_interface_url = old_case_dict.get("interface_url")
                old_depend_level = old_case_dict.get("depend_level")
                if interface_name != old_interface_name:
                    interface_name_case = pro_db.find_one({"interface_name": interface_name})
                    if interface_name_case: return "接口名称 已存在 ！"
                # if request_method != old_request_method or interface_url != old_interface_url:
                #     method_and_url_case = pro_db.find_one({"request_method": request_method, "interface_url": interface_url})
                #     if method_and_url_case: return "请求方式 + 接口地址 已存在 ！"
                if is_depend and depend_level != old_depend_level:
                    depend_level_case = pro_db.find_one({"depend_level": depend_level})
                    if depend_level_case: return "依赖等级 已存在 ！"
                # 更新用例
                pro_db.update({"_id": ObjectId(_id)}, {"$set": test_case_dict})
            else:  # add
                interface_name_case = pro_db.find_one({"interface_name": interface_name})
                if interface_name_case: return "接口名称 已存在 ！"
                # method_and_url_case = pro_db.find_one({"request_method": request_method, "interface_url": interface_url})
                # if method_and_url_case: return "请求方式 + 接口地址 已存在 ！"
                if is_depend:
                    depend_level_case = pro_db.find_one({"depend_level": depend_level})
                    if depend_level_case: return "依赖等级 已存在 ！"
                # 新增用例
                test_case_dict["response_info"] = ""
                test_case_dict["depend_field_value_list"] = []
                test_case_dict["actual_core_field_value_list"] = []
                test_case_dict["actual_field_name_list"] = []
                test_case_dict["downstream_relation_field_value_list"] = []
                test_case_dict["result_core_field_value"] = ""
                test_case_dict["result_field_name_list"] = ""
                test_case_dict["test_result"] = ""
                test_case_dict["run_status"] = False  # 该字段暂时不用
                test_case_dict["update_time"] = current_iso_date
                test_case_dict["create_time"] = current_iso_date
                pro_db.insert(test_case_dict)
        except Exception as e:
            log.error(e)
            mongo_exception_send_DD(e=e, msg="为'" + pro_name + "'项目'" + mode + "'测试用例")
            return "mongo error"
    return mode == "add" and "新增成功 ！" or "更新成功 ！"

    # dm = db_model(pro_name='pro_demo_1', table=cfg.TABLE_CASE, error_msg="新增或修改测试用例")
    # if mode == "edit":
    #     # 9.验证数据库中是否已存在
    #     old_case_dict = dm.query_db(query={"_id": ObjectId(_id)}, is_many=False)
    #     old_interface_name = old_case_dict.get("interface_name")
    #     old_request_method = old_case_dict.get("request_method")
    #     old_interface_url = old_case_dict.get("interface_url")
    #     old_depend_level = old_case_dict.get("depend_level")
    #     if interface_name != old_interface_name:
    #         interface_name_case = dm.query_db(query={"interface_name": interface_name}, is_many=False)
    #         if interface_name_case: return "接口名称 已存在 ！"
    #     # if request_method != old_request_method or interface_url != old_interface_url:
    #     #     method_and_url_case = dm.query_db(query={"request_method": request_method, "interface_url": interface_url},
    #     #                                       is_many=False)
    #     #     if method_and_url_case: return "请求方式 + 接口地址 已存在 ！"
    #     if is_depend and depend_level != old_depend_level:
    #         depend_level_case = dm.query_db(query={"depend_level": depend_level}, is_many=False)
    #         if depend_level_case: return "依赖等级 已存在 ！"
    #     # 更新用例
    #     dm.update_db(query={"_id": ObjectId(_id)}, set_dict=test_case_dict, is_many=True)
    # else:  # add
    #     interface_name_case = dm.query_db(query={"interface_name": interface_name}, is_many=False)
    #     if interface_name_case: return "接口名称 已存在 ！"
    #     # method_and_url_case = dm.query_db(query={"request_method": request_method, "interface_url": interface_url},
    #     #                                   is_many=False)
    #     # if method_and_url_case: return "请求方式 + 接口地址 已存在 ！"
    #     if is_depend:
    #         depend_level_case = dm.query_db(query={"depend_level": depend_level}, is_many=False)
    #         if depend_level_case: return "依赖等级 已存在 ！"
    #     # 新增用例
    #     test_case_dict["response_info"] = ""
    #     test_case_dict["depend_field_value_list"] = []
    #     test_case_dict["actual_core_field_value_list"] = []
    #     test_case_dict["actual_field_name_list"] = []
    #     test_case_dict["result_core_field_value"] = ""
    #     test_case_dict["result_field_name_list"] = ""
    #     test_case_dict["test_result"] = ""
    #     test_case_dict["run_status"] = False
    #     test_case_dict["update_time"] = current_iso_date
    #     test_case_dict["create_time"] = current_iso_date
    #     dm.insert_db(data=test_case_dict, is_many=True)


def get_config_del_result(request_json, pro_name):
    """
    获取配置删除结果 （ HOST | 全局变量 ）
    :param request_json:
    :param pro_name:
    :return:
    """
    # 判断是否在运行中
    if pro_is_running(pro_name):
        return "当前项目正在运行中"

    # 获取请求中的参数
    config_id = request_json.get("config_id", "").strip()
    query_dict = {"_id": ObjectId(config_id)}
    dm = db_model(pro_name=pro_name, table=cfg.TABLE_CONFIG, error_msg="删除HOST或全局配置")
    remove_case = dm.query_db(query=query_dict, is_many=False)
    if remove_case:
        dm.delete_db(query=query_dict, is_many=False)
        return "该配置删除成功 ！"
    else:
        return "要删除的配置不存在 ！ "


def get_case_del_result(request_json, pro_name):
    """
    获取用例删除结果
    :param request_json:
    :param pro_name:
    :return:
    """
    # 判断是否在运行中
    if pro_is_running(pro_name):
        return "当前项目正在运行中"

    # 获取请求中的参数
    _id = request_json.get("_id", "").strip()
    query_dict = {"_id": ObjectId(_id)}
    dm = db_model(pro_name=pro_name, table=cfg.TABLE_CASE, error_msg="删除测试用例")
    remove_case = dm.query_db(query=query_dict, is_many=False)
    if remove_case:
        dm.delete_db(query=query_dict, is_many=False)
        return "该用例删除成功 ！"
    else:
        return "要删除的用例不存在 ！ "


def get_case_by_name(request_json, pro_name):
    """
    获取已有用例
    :param request_json:
    :param pro_name:
    :return:
    """
    interface_name = request_json.get("interface_name", "").strip()
    dm = db_model(pro_name=pro_name, table=cfg.TABLE_CASE, error_msg="用过接口名称获取测试用例")
    exist_case_dict = dm.query_db(query={"interface_name": interface_name}, is_many=False)
    if exist_case_dict:
        # 将所有字段转换成 string 类型
        for field, value in exist_case_dict.items():
            # 若"验证模式、依赖等级"为0，则赋空值 传递给编辑弹层显示
            if field in ["verify_mode", "depend_level"]:
                exist_case_dict[field] = value != 0 and str(value) or ""
            if field in ["_id", "case_status", "is_depend", "create_time", "update_time", "run_status"]:
                exist_case_dict[field] = str(value)
            if field in get_list_field():
                exist_case_dict[field] = ",".join(map(lambda x: str(x), value))
    return exist_case_dict


def get_case_by_id(request_args, pro_name, table_tag):
    """
    通过id获取用例（填充编辑弹层）
    :param request_args:
    :param pro_name:
    :param table_tag:  _case | _result
    :return:
      【 字 段 格 式 】
      01.接口名称：interface_name（ 必填 ）
      02.接口地址：interface_url（ 必填 ）
      03.请求方式：request_method（ 必填 ）
      04.请求头文件：request_header
      05.请求参数：request_params
      06.验证模式：verify_mode（ 测试必填 ）                            < (Mongo)int -> (表单)string >
      07.待比较关键字段名列表：compare_core_field_name_list（ 测试必填 ）< (Mongo)list -> (表单)string >（以","分割）
      08.期望的关键字段值列表：expect_core_field_value_list（ 测试必填 ）< (Mongo)list -> (表单)string >（以","分割）
      09.期望的响应字段列表：expect_field_name_list                    < (Mongo)list -> (表单)string >（以","分割）
      10.上游关联用例：upstream_relation_case
      11.下游关联字段名列表：downstream_relation_field_name_list       < (Mongo)list -> (表单)string >（以","分割）
      12.是否为依赖接口：is_depend              （ 必填 ）             < (Mongo)bool -> (表单)string >
      13.依赖等级：depend_level                （ 依赖必填 ）          < (Mongo)int -> (表单)string >
      14.依赖字段名列表：depend_field_name_list                       < (Mongo)list -> (表单)string >（以","分割）
      15.用例状态：case_status                                       < (Mongo)bool -> (表单)string >

      < 以下字段不显示在导入表单中>
      16.响应状态码：http_code         < (Mongo)int -> (表单)string >
      17.响应时间(毫秒)：http_rt       < (Mongo)int -> (表单)string >
      18.响应信息：response_info
      19.依赖字段值列表：depend_field_value_list                  < (Mongo)list -> (表单)string >（以","分割）
      20.实际的关键字段值列表：actual_core_field_value_list        < (Mongo)list -> (表单)string >（以","分割）
      21.实际的响应字段列表：actual_field_name_list                < (Mongo)list -> (表单)string >（以","分割）
      22.下游关联字段值列表：downstream_relation_field_value_list  < (Mongo)list -> (表单)string >（以","分割）
      23.关键字段值比较结果：result_core_field_value
      24.响应字段列表比较结果：result_field_name_list
      25.测试结果：test_result
      26.运行状态：run_status    < (Mongo)bool -> (表单)string >
      27.创建时间：create_time   < (Mongo)ISODate -> (表单)string >
      28.更新时间：update_time   < (Mongo)ISODate -> (表单)string >

    """
    _id = request_args.get("_id", "").strip()
    dm = db_model(pro_name=pro_name, table=table_tag, error_msg="通过id获取测试用例")
    test_case_dict = dm.query_db(query={"_id": ObjectId(_id)}, is_many=False)

    # 将所有字段转换成 string 类型
    for field, value in test_case_dict.items():
        # 若"验证模式、依赖等级"为0，则赋空值 传递给编辑弹层显示
        if field in ["verify_mode", "depend_level"]:
            test_case_dict[field] = value != 0 and str(value) or ""

        if field in ["_id", "case_status", "is_depend", "create_time", "update_time", "run_status", "http_code", "http_rt"]:
            test_case_dict[field] = str(value)

        if field in get_list_field():
            test_case_dict[field] = ",".join(map(lambda x: str(x), value))

    return test_case_dict


def statis_case(pro_name):
    """
    统计用例：成功、失败、错误、下线、依赖
    :param pro_name:
    :return:
    【 步 骤 】
        1.获取所有用例
        2.将所有字段格式转换成string
        3.统计区分
        {"success_1":[], "fail_2":[], "error_1":[], "depend_2":[], "offline_3":[]}
    """
    # 获取所有用例
    dm = db_model(pro_name=pro_name, table=cfg.TABLE_CASE, error_msg="统计项目用例")
    all_case_list = dm.query_db(filter={"_id": 0}, is_many=True)

    # 将所有字段格式转换成string
    for case_dict in all_case_list:
        for field, value in case_dict.items():
            # 若"验证模式、依赖等级"为0，则赋空值
            if field in ["verify_mode", "depend_level"]:
                # case_dict[field] = value != 0 and str(value) or ""
                if value == 0:
                    case_dict[field] = ""
            if field in ["_id", "case_status", "is_depend", "run_status", "create_time", "update_time"]:
                case_dict[field] = str(value)
            if field in get_list_field():
                case_dict[field] = ",".join(map(lambda x: str(x), value))

    # 统计区分
    statis_dict = {}
    success_case_list = []
    fail_case_list = []
    error_case_list = []
    depend_case_list = []
    offline_case_list = []
    for case_dict in all_case_list:
        if case_dict["is_depend"] == "True":
            depend_case_list.append(case_dict)
        else:
            if case_dict["case_status"] == "False":
                offline_case_list.append(case_dict)
            else:
                if "success" in case_dict["test_result"]:
                    success_case_list.append(case_dict)
                elif "fail" in case_dict["test_result"]:
                    if "依赖接口" in case_dict["test_result"]:
                        error_case_list.append(case_dict)
                    else:
                        fail_case_list.append(case_dict)
                else:
                    error_case_list.append(case_dict)
    statis_dict["success_" + str(len(success_case_list))] = success_case_list
    statis_dict["fail_" + str(len(fail_case_list))] = fail_case_list
    statis_dict["error_" + str(len(error_case_list))] = error_case_list
    statis_dict["depend_" + str(len(depend_case_list))] = depend_case_list
    statis_dict["offline_" + str(len(offline_case_list))] = offline_case_list
    return statis_dict


def generate_excel_with_statis_case(pro_name):
    """
    生成测试报告 ( Excel )
    :param pro_name:
    :return:
    """
    if pro_is_running(pro_name):
        return "当前项目正在运行中"
    else:
        statis_dict = statis_case(pro_name)
        now = time.strftime("%Y_%m_%d-%H_%M_%S", time.localtime(time.time()))
        current_report_name = "[API_report]" + pro_name + "[" + now + "].xls"
        pro_report_path = cfg.REPORTS_DIR + pro_name + "/"
        history_report_path = pro_report_path + "history/"
        mkdir(history_report_path)
        current_report_file = history_report_path + current_report_name

        case_info_save_excel(excel_file=current_report_file, statis_dict=statis_dict)

        # 将最新报告替换../Reports/{{pro_name}}/下的[API_report]{{pro_name}}.xls
        res = os.system("cp " + current_report_file + " " + pro_report_path + " && "
                        "mv " + pro_report_path + current_report_name + " " + pro_report_path + "[API_report]" + pro_name + ".xls")
        if res != 0:
            log.error("测试报告替换操作有误！")
    return "生成完毕"


def case_info_save_excel(excel_file, statis_dict):
    """
    用例信息保存入 Excel
    :param excel_file:
    :param statis_dict:
    :return:

    < 备注 >
    1.标记 fail 和 error 工作表中的表头颜色
    （1）期望的关键字段值列表：第10列（蓝色：12）
    （2）期望的响应字段列表：  第11列（蓝色：12）
    （3）实际的关键字段值列表：第23列（紫色：20）
    （4）实际的响应字段列表：  第24列（紫色：20）
    （5）测试结果：          第27列（红色：2）

    2.标记 success 工作表中的表头颜色
    （1）期望的关键字段值列表：第10列（蓝色：12）
    （2）期望的响应字段列表：  第11列（蓝色：12）
    （3）实际的关键字段值列表：第23列（绿色：17）
    （4）实际的响应字段列表：  第24列（绿色：17）
    （5）测试结果：          第27列（绿色：17）

    3.标记 depend 工作表中的表头颜色
    （1）依赖字段名列表：第15列 （蓝色：12）
    （2）依赖字段值列表：第21列 （蓝色：12）
    （5）测试结果：     第26列 （绿色：17）

    """
    # 获取 excel head 数据
    field_name_dict = get_case_field_name()
    field_zn_list = list(field_name_dict.keys())
    field_cn_list = list(field_name_dict.values())

    # 获取 统计类别
    category_list = list(statis_dict.keys())

    # 将获取的数据存入excel中
    workbook = xlwt.Workbook(encoding='utf-8')
    for category in category_list:
        category_name = category.split("_")[0]
        category_num = category.split("_")[1]
        # 添加 工作表
        sheet = workbook.add_sheet(category_name + "(" + category_num + ")", cell_overwrite_ok=True)

        # 工作表 添加 head 数据
        colour = 0
        for cn_col_i in range(len(field_cn_list)):
            if category_name in ["fail", "error"]:
                colour = cn_col_i in [9, 10] and 12 or (cn_col_i in [22, 23] and 20 or (cn_col_i == 26 and 2 or 0))
            elif category_name == "success":
                colour = cn_col_i in [9, 10] and 12 or (cn_col_i in [22, 23, 26] and 17 or 0)
            elif category_name == "depend":
                colour = cn_col_i in [14, 20] and 12 or (cn_col_i == 25 and 17 or 0)
            sheet.write(0, cn_col_i, field_cn_list[cn_col_i], set_style(name=u"宋体", bold=True, colour=colour, size=250))
        for zn_col_i in range(len(field_zn_list)):
            sheet.write(1, zn_col_i, field_zn_list[zn_col_i], set_style(name=u"宋体", bold=True, colour=23, size=250))

        # 工作表 添加 数据
        case_list = statis_dict.get(category, [])
        for row_i, case_dict in enumerate(case_list):
            # 每列数据的插入方式：按照用例字典中的'key'去'field_zn_list'中匹配来获取列的索引，再将value插入
            for key, value in case_dict.items():
                col_i = field_zn_list.index(key)
                sheet.write(row_i + 2, col_i, str(value), set_style(name=u"宋体", bold=True, colour=0, size=250))
    workbook.save(excel_file)


if __name__ == "__main__":
    pro_name = "pro_demo_1"
    # verify_result, excel_list = verify_excel_and_transfer_format(cfg.UPLOAD_CASE_FILE)
    # print(verify_result)
    # print(excel_list)

    # statis_dict = statis_case("pro_demo_1")
    # for k, v in statis_dict.items():
    #     print(k)
    #     print(v)
    #     print("---------------")

    # generate_report_with_statis_case("pro_demo_1")
    # print(get_statist_data("pro_demo_1"))

    # clear_test_result("pro_demo_1", 0.16)
    # print(get_statist_data_for_case("pro_demo_1"))

    # update_case_status_by_tag(pro_name="pro_demo_1", case_status="True", tag_name="部署::回归::监控")

    # tag_list = screen_tag_name_by_test_time(pro_name=pro_name, test_time="2021-08-26 16:26:41")
    # for tag in tag_list:
    #     print(tag)

    # statist_data, interface_name_list = get_statist_data_for_result(pro_name=pro_name, test_time="2021-08-30 21:07:50")
    # print(statist_data)
    # print(interface_name_list)

    # res = pro_is_running(pro_name=pro_name)
    # print(res)
    # print(type(res))

    # get_relation_list(pro_name=pro_name)
    # relation_list = get_relation_list_by_case_name(pro_name=pro_name, case_name="场景三接口2")
    # for case in relation_list:
    #     print(case.get("interface_name"))

    print(check_relation_case_status(pro_name=pro_name, case_name="场景二接口1"))

